package info.izumin.android.bletia;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.content.Context;
import android.os.HandlerThread;
import android.support.test.runner.AndroidJUnit4;
import android.test.AndroidTestCase;
import org.jdeferred.DoneCallback;
import org.jdeferred.FailCallback;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.internal.util.reflection.Whitebox;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import info.izumin.android.bletia.action.EnableNotificationAction;
import info.izumin.android.bletia.action.ReadCharacteristicAction;
import info.izumin.android.bletia.action.ReadDescriptorAction;
import info.izumin.android.bletia.action.ReadRemoteRssiAction;
import info.izumin.android.bletia.action.WriteCharacteristicAction;
import info.izumin.android.bletia.action.WriteDescriptorAction;
import info.izumin.android.bletia.core.StateContainer;
import info.izumin.android.bletia.core.BleErrorType;
import info.izumin.android.bletia.core.BleMessageThread;
import info.izumin.android.bletia.core.BletiaException;
import info.izumin.android.bletia.core.BluetoothGattCallbackHandler;
import info.izumin.android.bletia.core.wrapper.BluetoothGattWrapper;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Created by izumin on 9/8/15.
*/
@RunWith(AndroidJUnit4.class)
public class BletiaTest extends AndroidTestCase {
@Mock private BluetoothGattCharacteristic mCharacteristic;
@Mock private BluetoothGattDescriptor mDescriptor;
@Mock private BluetoothGattWrapper mBluetoothGattWrapper;
private StateContainer mContainer;
private BleMessageThread mMessageThread;
private BluetoothGattCallbackHandler mCallbackHandler;
private Bletia mBletia;
private Context mContext;
private CountDownLatch mLatch;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mContext = getContext();
mBletia = new Bletia(mContext);
mContainer = (StateContainer) Whitebox.getInternalState(mBletia, "mContainer");
mCallbackHandler = mContainer.getCallbackHandler();
HandlerThread thread = new HandlerThread("test");
thread.start();
mMessageThread = new BleMessageThread(thread, mContainer);
mContainer.setMessageThread(mMessageThread);
mContainer.setGattWrapper(mBluetoothGattWrapper);
when(mCharacteristic.getUuid()).thenReturn(UUID.randomUUID());
when(mDescriptor.getUuid()).thenReturn(UUID.randomUUID());
when(mBluetoothGattWrapper.writeCharacteristic(mCharacteristic)).thenReturn(true);
when(mBluetoothGattWrapper.readCharacteristic(mCharacteristic)).thenReturn(true);
when(mBluetoothGattWrapper.writeDescriptor(mDescriptor)).thenReturn(true);
when(mBluetoothGattWrapper.readDescriptor(mDescriptor)).thenReturn(true);
when(mBluetoothGattWrapper.setCharacteristicNotification(eq(mCharacteristic), anyBoolean())).thenReturn(true);
final ArgumentCaptor<UUID> uuidCaptor = ArgumentCaptor.forClass(UUID.class);
final ArgumentCaptor<byte[]> valueCaptor = ArgumentCaptor.forClass(byte[].class);
when(mCharacteristic.getDescriptor(uuidCaptor.capture())).thenAnswer(new Answer<BluetoothGattDescriptor>() {
@Override
public BluetoothGattDescriptor answer(InvocationOnMock invocation) throws Throwable {
when(mDescriptor.getUuid()).thenReturn(uuidCaptor.getValue());
when(mDescriptor.getCharacteristic()).thenReturn(mCharacteristic);
return mDescriptor;
}
});
when(mDescriptor.setValue(valueCaptor.capture())).thenAnswer(new Answer<Boolean>() {
@Override
public Boolean answer(InvocationOnMock invocation) throws Throwable {
when(mDescriptor.getValue()).thenReturn(valueCaptor.getValue());
return true;
}
});
mLatch = new CountDownLatch(1);
}
@Override
public void tearDown() throws Exception {
mMessageThread.stop();
}
@Test
public void writeCharacteristicSuccessfully() throws Exception {
mBletia.writeCharacteristic(mCharacteristic)
.then(new DoneCallback<BluetoothGattCharacteristic>() {
@Override
public void onDone(BluetoothGattCharacteristic result) {
assertThat(result.getUuid()).isEqualTo(mCharacteristic.getUuid());
mLatch.countDown();
}
}).fail(mNeverCalledFailCallback);
Thread.sleep(300);
mCallbackHandler.onCharacteristicWrite(
mBluetoothGattWrapper, mCharacteristic, BluetoothGatt.GATT_SUCCESS);
await();
}
@Test
public void writeCharacteristicFailure() throws Exception {
mBletia.writeCharacteristic(mCharacteristic)
.done(mNeverCalledDoneCallback)
.fail(new FailCallback<BletiaException>() {
@Override
public void onFail(BletiaException result) {
assertThat(result.getType()).isEqualTo(BleErrorType.FAILURE);
assertThat(result.getAction()).isInstanceOf(WriteCharacteristicAction.class);
mLatch.countDown();
}
});
Thread.sleep(300);
mCallbackHandler.onCharacteristicWrite(
mBluetoothGattWrapper, mCharacteristic, BluetoothGatt.GATT_FAILURE);
await();
}
@Test
public void writeCharacteristicWhenOperationIsInitiatedFailure() throws Exception {
when(mBluetoothGattWrapper.writeCharacteristic(any(BluetoothGattCharacteristic.class))).thenReturn(false);
mBletia.writeCharacteristic(mCharacteristic)
.done(mNeverCalledDoneCallback)
.fail(new FailCallback<BletiaException>() {
@Override
public void onFail(BletiaException result) {
assertThat(result.getType()).isEqualTo(BleErrorType.OPERATION_INITIATED_FAILURE);
assertThat(result.getAction()).isInstanceOf(WriteCharacteristicAction.class);
mLatch.countDown();
}
});
await();
}
@Test
public void readCharacteristicSuccessfully() throws Exception {
mBletia.readCharacteristic(mCharacteristic)
.then(new DoneCallback<BluetoothGattCharacteristic>() {
@Override
public void onDone(BluetoothGattCharacteristic result) {
assertThat(result.getUuid()).isEqualTo(mCharacteristic.getUuid());
mLatch.countDown();
}
}).fail(mNeverCalledFailCallback);
Thread.sleep(300);
mCallbackHandler.onCharacteristicRead(
mBluetoothGattWrapper, mCharacteristic, BluetoothGatt.GATT_SUCCESS);
await();
}
@Test
public void readCharacteristicFailure() throws Exception {
mBletia.readCharacteristic(mCharacteristic)
.done(mNeverCalledDoneCallback)
.fail(new FailCallback<BletiaException>() {
@Override
public void onFail(BletiaException result) {
assertThat(result.getType()).isEqualTo(BleErrorType.FAILURE);
assertThat(result.getAction()).isInstanceOf(ReadCharacteristicAction.class);
mLatch.countDown();
}
});
Thread.sleep(300);
mCallbackHandler.onCharacteristicRead(
mBluetoothGattWrapper, mCharacteristic, BluetoothGatt.GATT_FAILURE);
await();
}
@Test
public void readCharacteristicWhenOperationIsInitiatedFailure() throws Exception {
when(mBluetoothGattWrapper.readCharacteristic(any(BluetoothGattCharacteristic.class))).thenReturn(false);
mBletia.readCharacteristic(mCharacteristic)
.done(mNeverCalledDoneCallback)
.fail(new FailCallback<BletiaException>() {
@Override
public void onFail(BletiaException result) {
assertThat(result.getType()).isEqualTo(BleErrorType.OPERATION_INITIATED_FAILURE);
assertThat(result.getAction()).isInstanceOf(ReadCharacteristicAction.class);
mLatch.countDown();
}
});
await();
}
public void writeDescriptorSuccessfully() throws Exception {
mBletia.writeDescriptor(mDescriptor).then(new DoneCallback<BluetoothGattDescriptor>() {
@Override
public void onDone(BluetoothGattDescriptor result) {
assertThat(result.getUuid()).isEqualTo(mDescriptor.getUuid());
mLatch.countDown();
}
}).fail(mNeverCalledFailCallback);
Thread.sleep(300);
mCallbackHandler.onDescriptorWrite(
mBluetoothGattWrapper, mDescriptor, BluetoothGatt.GATT_SUCCESS);
await();
}
@Test
public void writeDescriptorFailure() throws Exception {
mBletia.writeDescriptor(mDescriptor)
.done(mNeverCalledDoneCallback)
.fail(new FailCallback<BletiaException>() {
@Override
public void onFail(BletiaException result) {
assertThat(result.getType()).isEqualTo(BleErrorType.FAILURE);
assertThat(result.getAction()).isInstanceOf(WriteDescriptorAction.class);
mLatch.countDown();
}
});
Thread.sleep(300);
mCallbackHandler.onDescriptorWrite(
mBluetoothGattWrapper, mDescriptor, BluetoothGatt.GATT_FAILURE);
await();
}
@Test
public void writeDescriptorWhenOperationIsInitiatedFailure() throws Exception {
when(mBluetoothGattWrapper.writeDescriptor(any(BluetoothGattDescriptor.class))).thenReturn(false);
mBletia.writeDescriptor(mDescriptor)
.done(mNeverCalledDoneCallback)
.fail(new FailCallback<BletiaException>() {
@Override
public void onFail(BletiaException result) {
assertThat(result.getType()).isEqualTo(BleErrorType.OPERATION_INITIATED_FAILURE);
assertThat(result.getAction()).isInstanceOf(WriteDescriptorAction.class);
mLatch.countDown();
}
});
await();
}
@Test
public void readDescriptorSuccessfully() throws Exception {
mBletia.readDescriptor(mDescriptor)
.then(new DoneCallback<BluetoothGattDescriptor>() {
@Override
public void onDone(BluetoothGattDescriptor result) {
assertThat(result.getUuid()).isEqualTo(mDescriptor.getUuid());
mLatch.countDown();
}
})
.fail(mNeverCalledFailCallback);
Thread.sleep(300);
mCallbackHandler.onDescriptorRead(
mBluetoothGattWrapper, mDescriptor, BluetoothGatt.GATT_SUCCESS);
await();
}
@Test
public void readDescriptorFailure() throws Exception {
mBletia.readDescriptor(mDescriptor)
.done(mNeverCalledDoneCallback)
.fail(new FailCallback<BletiaException>() {
@Override
public void onFail(BletiaException result) {
assertThat(result.getType()).isEqualTo(BleErrorType.FAILURE);
assertThat(result.getAction()).isInstanceOf(ReadDescriptorAction.class);
mLatch.countDown();
}
});
Thread.sleep(300);
mCallbackHandler.onDescriptorRead(
mBluetoothGattWrapper, mDescriptor, BluetoothGatt.GATT_FAILURE);
await();
}
@Test
public void readDescriptorWhenOperationIsInitiatedFailure() throws Exception {
when(mBluetoothGattWrapper.readDescriptor(any(BluetoothGattDescriptor.class))).thenReturn(false);
mBletia.readDescriptor(mDescriptor)
.done(mNeverCalledDoneCallback)
.fail(new FailCallback<BletiaException>() {
@Override
public void onFail(BletiaException result) {
assertThat(result.getType()).isEqualTo(BleErrorType.OPERATION_INITIATED_FAILURE);
assertThat(result.getAction()).isInstanceOf(ReadDescriptorAction.class);
mLatch.countDown();
}
});
await();
}
@Test
public void enableNotificationSuccessfully() throws Exception {
mBletia.enableNotification(mCharacteristic, true)
.done(new DoneCallback<BluetoothGattCharacteristic>() {
@Override
public void onDone(BluetoothGattCharacteristic result) {
mLatch.countDown();
}
})
.fail(mNeverCalledFailCallback);
Thread.sleep(300);
mCallbackHandler.onDescriptorWrite(mBluetoothGattWrapper, mDescriptor, BluetoothGatt.GATT_SUCCESS);
await();
}
@Test
public void enableNotificationWhenRequestFailure() throws Exception {
when(mBluetoothGattWrapper.setCharacteristicNotification(eq(mCharacteristic), anyBoolean())).thenReturn(false);
mBletia.enableNotification(mCharacteristic, true)
.done(mNeverCalledDoneCallback)
.fail(new FailCallback<BletiaException>() {
@Override
public void onFail(BletiaException result) {
assertThat(result.getType()).isEqualTo(BleErrorType.REQUEST_FAILURE);
assertThat(result.getAction()).isInstanceOf(EnableNotificationAction.class);
mLatch.countDown();
}
});
await();
}
@Test
public void enableNotificationWhenOperationInitiatedFailure() throws Exception {
when(mBluetoothGattWrapper.writeDescriptor(mDescriptor)).thenReturn(false);
mBletia.enableNotification(mCharacteristic, true)
.done(mNeverCalledDoneCallback)
.fail(new FailCallback<BletiaException>() {
@Override
public void onFail(BletiaException result) {
assertThat(result.getType()).isEqualTo(BleErrorType.OPERATION_INITIATED_FAILURE);
assertThat(result.getAction()).isInstanceOf(EnableNotificationAction.class);
mLatch.countDown();
}
});
await();
}
@Test
public void enableNotificationWhenOperationFailure() throws Exception {
mBletia.enableNotification(mCharacteristic, true)
.done(mNeverCalledDoneCallback)
.fail(new FailCallback<BletiaException>() {
@Override
public void onFail(BletiaException result) {
assertThat(result.getType()).isEqualTo(BleErrorType.FAILURE);
assertThat(result.getAction()).isInstanceOf(EnableNotificationAction.class);
mLatch.countDown();
}
});
Thread.sleep(300);
verify(mBluetoothGattWrapper, times(1)).writeDescriptor(mDescriptor);
mCallbackHandler.onDescriptorWrite(
mBluetoothGattWrapper, mDescriptor, BluetoothGatt.GATT_FAILURE);
await();
}
@Test
public void disableNotificationSuccessfully() throws Exception {
mBletia.enableNotification(mCharacteristic, false)
.done(new DoneCallback<BluetoothGattCharacteristic>() {
@Override
public void onDone(BluetoothGattCharacteristic result) {
mLatch.countDown();
}
})
.fail(mNeverCalledFailCallback);
Thread.sleep(300);
verify(mBluetoothGattWrapper, times(1)).writeDescriptor(mDescriptor);
mCallbackHandler.onDescriptorWrite(mBluetoothGattWrapper, mDescriptor, BluetoothGatt.GATT_SUCCESS);
await();
}
@Test
public void disableNotificationWhenRequestFailure() throws Exception {
when(mBluetoothGattWrapper.setCharacteristicNotification(eq(mCharacteristic), anyBoolean())).thenReturn(false);
mBletia.enableNotification(mCharacteristic, false)
.done(mNeverCalledDoneCallback)
.fail(new FailCallback<BletiaException>() {
@Override
public void onFail(BletiaException result) {
assertThat(result.getType()).isEqualTo(BleErrorType.REQUEST_FAILURE);
assertThat(result.getAction()).isInstanceOf(EnableNotificationAction.class);
mLatch.countDown();
mLatch.countDown();
}
});
await();
}
@Test
public void disableNotificationWhenOperationInitiatedFailure() throws Exception {
when(mBluetoothGattWrapper.writeDescriptor(mDescriptor)).thenReturn(false);
mBletia.enableNotification(mCharacteristic, false)
.done(mNeverCalledDoneCallback)
.fail(new FailCallback<BletiaException>() {
@Override
public void onFail(BletiaException result) {
assertThat(result.getType()).isEqualTo(BleErrorType.OPERATION_INITIATED_FAILURE);
assertThat(result.getAction()).isInstanceOf(EnableNotificationAction.class);
mLatch.countDown();
}
});
await();
}
@Test
public void disableNotificationWhenOperationFailure() throws Exception {
mBletia.enableNotification(mCharacteristic, false)
.done(mNeverCalledDoneCallback)
.fail(new FailCallback<BletiaException>() {
@Override
public void onFail(BletiaException result) {
assertThat(result.getType()).isEqualTo(BleErrorType.FAILURE);
assertThat(result.getAction()).isInstanceOf(EnableNotificationAction.class);
mLatch.countDown();
}
});
Thread.sleep(300);
verify(mBluetoothGattWrapper, times(1)).writeDescriptor(mDescriptor);
mCallbackHandler.onDescriptorWrite(
mBluetoothGattWrapper, mDescriptor, BluetoothGatt.GATT_FAILURE);
await();
}
@Test
public void readRemoteRssiSuccessfully() throws Exception {
when(mBluetoothGattWrapper.readRemoteRssi()).thenReturn(true);
mBletia.readRemoteRssi()
.done(new DoneCallback<Integer>() {
@Override
public void onDone(Integer result) {
assertThat(result).isEqualTo(100);
mLatch.countDown();
}
})
.fail(mNeverCalledFailCallback);
Thread.sleep(300);
mCallbackHandler.onReadRemoteRssi(
mBluetoothGattWrapper, 100, BluetoothGatt.GATT_SUCCESS);
await();
}
@Test
public void readRemoteRssiRequestFailure() throws Exception {
when(mBluetoothGattWrapper.readRemoteRssi()).thenReturn(false);
mBletia.readRemoteRssi()
.done(mNeverCalledDoneCallback)
.fail(new FailCallback<BletiaException>() {
@Override
public void onFail(BletiaException result) {
assertThat(result.getType()).isEqualTo(BleErrorType.REQUEST_FAILURE);
assertThat(result.getAction()).isInstanceOf(ReadRemoteRssiAction.class);
mLatch.countDown();
}
});
await();
}
@Test
public void readRemoteRssiOperationFailure() throws Exception {
when(mBluetoothGattWrapper.readRemoteRssi()).thenReturn(true);
mBletia.readRemoteRssi()
.done(mNeverCalledDoneCallback)
.fail(new FailCallback<BletiaException>() {
@Override
public void onFail(BletiaException result) {
assertThat(result.getType()).isEqualTo(BleErrorType.FAILURE);
assertThat(result.getAction()).isInstanceOf(ReadRemoteRssiAction.class);
mLatch.countDown();
}
});
Thread.sleep(300);
mCallbackHandler.onReadRemoteRssi(
mBluetoothGattWrapper, 100, BluetoothGatt.GATT_FAILURE);
await();
}
private void await() throws InterruptedException {
boolean res = mLatch.await(1000, TimeUnit.MILLISECONDS);
assertThat(res).isTrue();
}
private DoneCallback mNeverCalledDoneCallback = new DoneCallback() {
@Override public void onDone(Object result) { fail(); }
};
private FailCallback mNeverCalledFailCallback = new FailCallback() {
@Override public void onFail(Object result) { fail(); }
};
}